DLO-JZ Optimisation de l'apprentissage - Jour 1¶

Optimisation système d'une boucle d'apprentissage Resnet-152.

car

Objet du notebook¶

Le but de ce notebook est d'optimiser un code d'apprentissage d'un modèle Resnet-50 sur Imagenet pour Jean Zay en implémentant :

  • TP 1 : l'accélération GPU
  • TP 2 : l'Automatic Mixed Precision
  • TP 3 : le Channels Last Memory Format
  • TP 4 : le Profiler

Les cellules dans ce notebook ne sont pas prévues pour être modifiées, sauf rares exceptions indiquées dans les commentaires. Les TP se feront en modifiant les codes dlojz1_X.py.

Les directives de modification seront marquées par l'étiquette TODO dans le notebook suivant.

Les solutions sont présentes dans le répertoire solutions/.

Notebook rédigé par l'équipe assistance IA de l'IDRIS, juin 2023


Environnement de calcul¶

Les fonctions python de gestion de queue SLURM dévelopées par l'IDRIS et les fonctions dédiées à la formation DLO-JZ sont à importer.

Le module d'environnement pour les jobs et la taille des images sont fixés pour ce notebook.

TODO : choisir un pseudonyme (maximum 5 caractères) pour vous différencier dans la queue SLURM et dans les outils collaboratifs pendant la formation et la compétition.

In [1]:
from idr_pytools import display_slurm_queue, gpu_jobs_submitter, search_log
from dlojz_tools import controle_technique, compare, GPU_underthehood, plot_accuracy, lrfind_plot, imagenet_starter
MODULE = 'pytorch-gpu/py3/2.3.0'
image_size = 224
account = 'for@a100'
name = 'pseudo'   ## Pseudonyme à choisir

Création d'un répertoire checkpoints/ si cela n'a pas déjà été fait.

In [2]:
!mkdir -p checkpoints



Gestion de la queue SLURM¶

Pour afficher vos jobs dans la queue SLURM :

In [7]:
display_slurm_queue(name)
 Done!

Remarque: Cette fonction est utilisée plusieurs fois dans ce notebook. Elle permet d'afficher la queue de manière dynamique, rafraichie toutes les 5 secondes. Elle ne s'arrête que lorsque la queue est vide. Si vous désirez reprendre la main sur le notebook, il vous suffira d'arrêter manuellement la cellule avec le bouton stop. Cela a bien sûr aucun impact les jobs soumis.

Si vous voulez retirer TOUS vos jobs de la queue SLURM, décommenter et exécuter la cellule suivante :

In [8]:
#!scancel -u $USER

Si vous voulez retirer UN de vos jobs de la queue SLURM, décommenter, compléter et exécuter la cellule suivante :

In [9]:
#!scancel <jobid>

Différence entre deux scripts¶

Pour comparer son code avec les solutions mises à disposition, la fonction suivante permet d'afficher une page HTML contenant un différentiel de fichiers texte.

In [14]:
s1 = "./dlojz1_4.py"
s2 = "./solutions/dlojz1_4.py"
compare(s1, s2)

Voir le résultat du différentiel de fichiers sur la page suivante (attention au spoil !) :

compare.html


Dataset et modèle¶

Cette partie permet de visualiser les caractéristiques du dataset et du modèle utilisés.

Imagenet¶

Train set¶

In [15]:
import os
import torchvision
import torchvision.transforms as transforms
import torch
import numpy as np
import matplotlib.pyplot as plt

transform = transforms.Compose([ 
        transforms.RandomResizedCrop(224),             # Random resize - Data Augmentation
        transforms.RandomHorizontalFlip(),              # Horizontal Flip - Data Augmentation
        transforms.ToTensor(),                          # convert the PIL Image to a tensor
        transforms.Normalize(mean=(0.485, 0.456, 0.406),
                             std=(0.229, 0.224, 0.225))
        ])
    
    
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
                                                  transform=transform)
train_dataset
Out[15]:
Dataset ImageNet
    Number of datapoints: 1281167
    Root location: /lustre/fsn1/projects/idris/for/commun/imagenet
    Split: train
    StandardTransform
Transform: Compose(
               RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=True)
               RandomHorizontalFlip(p=0.5)
               ToTensor()
               Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
           )
In [16]:
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,    
                                           batch_size=4,
                                           shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
      .format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
      .format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))

img = batch[0][0].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
labels_cls, labels_id = torch.load(os.environ['ALL_CCFRSCRATCH']+'/imagenet/meta.bin')
label = labels_cls[np.unique(labels_id)[batch[1][0].numpy()]]
_ = plt.title('label class: {}'.format(label[0]))
X train batch, shape: torch.Size([4, 3, 224, 224]), data type: torch.float32, Memory usage: 2408448 bytes
Y train batch, shape: torch.Size([4]), data type: torch.int64, Memory usage: 32 bytes
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [-2.117904..2.3262744].
No description has been provided for this image

Validation set¶

In [17]:
val_transform = transforms.Compose([
                                    transforms.Resize((256, 256)),
                                    transforms.CenterCrop(224),
                                    transforms.ToTensor(),   # convert the PIL Image to a tensor
                                    transforms.Normalize(mean=(0.485, 0.456, 0.406),
                                    std=(0.229, 0.224, 0.225))])

val_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet', split='val',
                        transform=val_transform)
val_dataset
Out[17]:
Dataset ImageNet
    Number of datapoints: 50000
    Root location: /lustre/fsn1/projects/idris/for/commun/imagenet
    Split: val
    StandardTransform
Transform: Compose(
               Resize(size=(256, 256), interpolation=bilinear, max_size=None, antialias=True)
               CenterCrop(size=(224, 224))
               ToTensor()
               Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
           )

Resnet-152¶

In [18]:
import torchvision.models as models
model = models.resnet152()
print('number of total parameters: {}'.format(sum([p.numel() for p in model.parameters()])))
print('number of trainable parameters: {}'.format(sum([p.numel() for p in model.parameters() if p.requires_grad])))
number of total parameters: 60192808
number of trainable parameters: 60192808

TP1_0 : Baseline CPU¶

Ce TP consiste à appliquer le code baseline pour prendre en main les fonctionnalités de test et découvrir le code.

TODO :

  1. Exécuter les cellules suivantes (le job prend plus de 10 minutes environ)
  2. Puis, ouvrir le fichier dlojz1_0.py

Remarque :

  • l'option test lance un apprentissage de 50 itérations.
  • les chronomètres mesurent les temps de la 2e à la 50e itération et restitue un temps moyen par itération.
  • les parties DON'T MODIFY dans le script ne doivent pas être modifiées.
In [19]:
n_gpu = 1
batch_size = 128
In [20]:
command = f'./dlojz1_0.py -b {batch_size} --image-size {image_size} --test --no-pin-memory --test-nsteps 10'
command
Out[20]:
'./dlojz1_0.py -b 128 --image-size 224 --test --no-pin-memory --test-nsteps 10'

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [21]:
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                   account=account, time_max='00:20:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249368
jobid = ['249368']

Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [22]:
#jobid = ['159506']
In [23]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249368    gpu_p5   pseudo  cfor032  R       9:27      1 jean-zay-iam10

 Done!

Quizz¶

L'éxécution étant assez longue (~ 10 min.), un quizz vous attend : Quizz TP1_0

In [24]:
controle_technique(jobid)
Train throughput: 5.45 images/second
GPU throughput: 5.46 images/second
epoch time: 234907.75 seconds
-----------
training step time average (fwd/bkwd on GPU): 23.447189 sec (35.1%/64.9%) +/- 0.164168
loading step time average (IO + CPU to GPU transfer): 0.020119 sec +/- 0.008249

Click here to display the log file

Le code baseline dlojz1_0.py a été exécuté sur le CPU (contrairement à ce qui est indiqué par le contrôle technique) en mode test, soit sur 50 itérations.

Dans le prochain exercice nous verrons ensemble l'accélération sur 1 GPU.

Garage


TP1_1 : Accélération GPU¶

Voir la documentation pytorch

TODO : dans le script dlojz1_1.py:

  • Définir la variable gpu et envoyer le modèle dans la mémoire du GPU.

  • Envoyer les batches d'images d'entrée et les labels associés sur le GPU, pour **les étapes de training et de *validation***.

In [25]:
n_gpu = 1
batch_size = 128
command = f'./dlojz1_1.py -b {batch_size} --image-size {image_size} --test'
command
Out[25]:
'./dlojz1_1.py -b 128 --image-size 224 --test'

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [26]:
command = f'./dlojz1_1.py -b {batch_size} --image-size {image_size} --test'
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                   account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249432
jobid = ['249432']

Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [27]:
#jobid = ['159564']
In [28]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249432    gpu_p5   pseudo  cfor032  R       0:35      1 jean-zay-iam10

 Done!

Quizz¶

L'éxécution étant assez longue, un quizz vous attend : Quizz TP1_1

In [29]:
controle_technique(jobid)
Train throughput: 468.86 images/second
GPU throughput: 474.73 images/second
epoch time: 2732.77 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.269628 sec (8.2%/91.8%) +/- 0.000315
loading step time average (IO + CPU to GPU transfer): 0.003376 sec +/- 0.000060

Click here to display the log file

Test d'occupation mémoire¶

Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [30]:
n_gpu = 1
batch_size = [8, 16, 32, 64, 128, 256, 512]
command = [f'./dlojz1_1.py -b {b} --image-size {image_size} --test'
          for b in batch_size]
jobids = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                   account=account, time_max='00:10:00')
print(f'jobids = {jobids}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249436
Submitted batch job 249437
Submitted batch job 249438
Submitted batch job 249439
Submitted batch job 249440
Submitted batch job 249442
Submitted batch job 249443
jobids = ['249436', '249437', '249438', '249439', '249440', '249442', '249443']

Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [31]:
#jobids = ['902357', '902358', '902359', '902360', '902361', '902362', '902363', '902365']
In [32]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249443    gpu_p5   pseudo  cfor032  R       1:01      1 jean-zay-iam07

 Done!
In [33]:
GPU_underthehood(jobids)
Batch size per GPU: 8 Max GPU Memory Allocated: 3.34 GB, Troughput: 180.269 images/second
Batch size per GPU: 16 Max GPU Memory Allocated: 3.35 GB, Troughput: 307.748 images/second
Batch size per GPU: 32 Max GPU Memory Allocated: 5.82 GB, Troughput: 420.239 images/second
Batch size per GPU: 64 Max GPU Memory Allocated: 11.19 GB, Troughput: 452.256 images/second
Batch size per GPU: 128 Max GPU Memory Allocated: 21.75 GB, Troughput: 473.613 images/second
Batch size per GPU: 256 Max GPU Memory Allocated: 43.03 GB, Troughput: 487.266 images/second
Batch size per GPU: 512 CUDA out of memory
Memory occupancy by Model part : 0.606 +/- 0.172 GB

Le dernier job a atteint le seuil CUDA Out Of Memory :

In [34]:
controle_technique([jobids[-1]])

Please check here the error log

Garage


TP1_2 : Automatic Mixed Precision¶

Voir la documentation de l'IDRIS

TODO : dans le script dlojz1_2.py:

  • Importer les fonctionnalités liées à l'Automatic Mixed Precision.

  • Initialiser le scaler.

  • Implémenter l'autocasting (le changement de précision, FP32 à FP16) dans le forward , avec la ligne with autocast(): dans la boucle de training et la boucle de validation.

  • Implémenter le gradient scaling pour la seule boucle de training. Note: À la place des lignes loss.backward() et optimizer.step().

In [40]:
n_gpu = 1
batch_size = 128
command = f'./dlojz1_2.py -b {batch_size} --image-size {image_size} --test'
command
Out[40]:
'./dlojz1_2.py -b 128 --image-size 224 --test'

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [41]:
command = f'./dlojz1_2.py -b {batch_size} --image-size {image_size} --test'
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249456
jobid = ['249456']

Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [42]:
#jobid = ['902431']
In [43]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249456    gpu_p5   pseudo  cfor032  R       0:37      1 jean-zay-iam12

 Done!

Quizz¶

L'éxécution étant assez longue, un quizz vous attend : Quizz TP1_2

In [44]:
controle_technique(jobid)
Train throughput: 736.12 images/second
GPU throughput: 751.12 images/second
epoch time: 1740.59 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.170413 sec (15.9%/84.3%) +/- 0.003061
loading step time average (IO + CPU to GPU transfer): 0.003472 sec +/- 0.000166

Click here to display the log file

Test d'occupation mémoire¶

Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [45]:
n_gpu = 1
batch_size = [16, 32, 64, 128, 256, 512, 1024]
command = [f'./dlojz1_2.py -b {b} --image-size {image_size} --test'
          for b in batch_size]
jobids = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                   account=account, time_max='00:10:00')
print(f'jobids = {jobids}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249458
Submitted batch job 249476
Submitted batch job 249478
Submitted batch job 249479
Submitted batch job 249480
Submitted batch job 249481
Submitted batch job 249482
jobids = ['249458', '249476', '249478', '249479', '249480', '249481', '249482']

Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [46]:
#jobids = ['903148', '903150', '903151', '903152', '903154', '903155', '903156', '903157']
In [47]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249481    gpu_p5   pseudo  cfor032  R       1:23      1 jean-zay-iam21

 Done!
In [48]:
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 2.20 GB, Troughput: 302.401 images/second
Batch size per GPU: 32 Max GPU Memory Allocated: 3.36 GB, Troughput: 527.546 images/second
Batch size per GPU: 64 Max GPU Memory Allocated: 6.02 GB, Troughput: 678.685 images/second
Batch size per GPU: 128 Max GPU Memory Allocated: 11.48 GB, Troughput: 750.371 images/second
Batch size per GPU: 256 Max GPU Memory Allocated: 22.20 GB, Troughput: 733.232 images/second
Batch size per GPU: 512 Max GPU Memory Allocated: 43.84 GB, Troughput: 753.701 images/second
Batch size per GPU: 1024 CUDA out of memory
Memory occupancy by Model part : 0.645 +/- 0.084 GB

Changement de taille de batch¶

TODO : Choisir pour la suite du TP une taille de batch par GPU qui vous semble la plus pertinente selon le test précédent.

In [49]:
## Choisir un batch size optimal
bs_optim = 512
In [50]:
n_gpu = 1
command = f'./dlojz1_2.py -b {bs_optim} --image-size {image_size} --test'
command
Out[50]:
'./dlojz1_2.py -b 512 --image-size 224 --test'

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [51]:
command = f'./dlojz1_2.py -b {bs_optim} --image-size {image_size} --test'
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249542
jobid = ['249542']

Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [52]:
#jobid = ['903167']
In [53]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249542    gpu_p5   pseudo  cfor032 CG       1:08      1 jean-zay-iam12

 Done!
In [54]:
controle_technique(jobid)
Train throughput: 737.36 images/second
GPU throughput: 751.50 images/second
epoch time: 1738.00 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.681303 sec (7.6%/92.5%) +/- 0.008765
loading step time average (IO + CPU to GPU transfer): 0.013063 sec +/- 0.000205

Click here to display the log file

Commentaires


TP1_3 : Channels Last Memory Format¶

Voir la documentation pytorch

TODO : dans le script dlojz1_3.py:

  • Lors de l'envoie du modèle au GPU, configurer le paramètre memory_format avec l'option Channel Last Memory.

  • Lors de l'envoie des images d'entrée au GPU, configurer le paramètre memory_format avec l'option Channel Last Memory.

In [55]:
n_gpu = 1
command = f'./dlojz1_3.py -b {bs_optim} --image-size {image_size} --test'
command
Out[55]:
'./dlojz1_3.py -b 512 --image-size 224 --test'

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [56]:
command = f'./dlojz1_3.py -b {bs_optim} --image-size {image_size} --test'
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249549
jobid = ['249549']

Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [57]:
#jobid = ['902659']
In [58]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249549    gpu_p5   pseudo  cfor032  R       0:50      1 jean-zay-iam12

 Done!
In [59]:
controle_technique(jobid)
Train throughput: 1162.16 images/second
GPU throughput: 1199.55 images/second
epoch time: 1102.72 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.426828 sec (6.4%/94.0%) +/- 0.012713
loading step time average (IO + CPU to GPU transfer): 0.013729 sec +/- 0.000413

Click here to display the log file

Test d'occupation mémoire¶

Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [60]:
n_gpu = 1
batch_size = [16, 32, 64, 128, 256, 512, 1024]
command = [f'./dlojz1_3.py -b {b} --image-size {image_size} --test'
          for b in batch_size]
jobids = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                   account=account, time_max='00:10:00')
print(f'jobids = {jobids}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249557
Submitted batch job 249558
Submitted batch job 249559
Submitted batch job 249561
Submitted batch job 249562
Submitted batch job 249563
Submitted batch job 249564
jobids = ['249557', '249558', '249559', '249561', '249562', '249563', '249564']

Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [61]:
#jobids = ['2069429', '2069430', '2069431', '2069432', '2069433', '2069435', '2069437']
In [62]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249564    gpu_p5   pseudo  cfor032 CG       1:10      1 jean-zay-iam21

 Done!
In [63]:
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 2.10 GB, Troughput: 307.175 images/second
Batch size per GPU: 32 Max GPU Memory Allocated: 3.31 GB, Troughput: 570.445 images/second
Batch size per GPU: 64 Max GPU Memory Allocated: 6.01 GB, Troughput: 966.245 images/second
Batch size per GPU: 128 Max GPU Memory Allocated: 11.48 GB, Troughput: 1110.966 images/second
Batch size per GPU: 256 Max GPU Memory Allocated: 22.22 GB, Troughput: 1158.450 images/second
Batch size per GPU: 512 Max GPU Memory Allocated: 43.88 GB, Troughput: 1196.800 images/second
Batch size per GPU: 1024 CUDA out of memory
Memory occupancy by Model part : 0.612 +/- 0.077 GB

Garage


Garage - Mise à niveau¶

On fixe le batch size et la taille d'image pour ce TP.

In [64]:
image_size = 224

TP1_4 : Profiler¶

Implémentation du profiler PyTorch¶

Voir la documentation de l'IDRIS.

TODO : dans le script dlojz1_4.py :

  • Importer les librairies liées au profiler PyTorch.

  • Configurer le profiler et ses paramètres.

# pytorch profiler setup
	prof =  profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
                    schedule=schedule(wait=1, warmup=1, active=5, repeat=1),
                    on_trace_ready=tensorboard_trace_handler('./profiler/' + os.environ['SLURM_JOB_NAME'] 
                                               + '_' + os.environ['SLURM_JOBID'] + '_bs' +
                                               str(mini_batch_size)  + '_is' + str(args.image_size)),
                    profile_memory=True,
                    record_shapes=False, 
                    with_stack=False,
                    with_flops=False
                    )
  • Englober toute la boucle d'apprentissage (validation comprise) dans le context prof.

  • Indiquer au profiler la fin de chaque itération d'apprentissage (avant la validation).

  • Ajouter des balises comme suit :

#TODO tag forward step with record functions
    with record_function("optimizer zero grad"): optimizer.zero_grad()
    # Implement autocasting
    with autocast():
        with record_function("inference"): outputs = model(images)
        with record_function("loss function"): loss = criterion(outputs, labels)
    
    if args.test: chrono.backward()       

    #TODO tag backward step with record functions
    # Implement gradient scaling
    with record_function("gradient compute"): scaler.scale(loss).backward()
    with record_function("optimizer step and weights update"): scaler.step(optimizer)
    with record_function("Scaler update"): scaler.update()

Génération d'une trace profiler¶

Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.

Remarques :

  • le profilage sera actif sur 5 steps donc nous n'exécutons l'entraînement que sur 8 steps grâce à l'argument --test-nsteps 8.
  • Nous lancerons deux jobs de prise de trace, avec une taille de batch de 512 puis une taille de batch de 64.

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [65]:
command = [f'./dlojz1_4.py -b 512 --image-size {image_size} --test --test-nsteps 8',
           f'./dlojz1_4.py -b 64 --image-size {image_size} --test --test-nsteps 8']
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249566
Submitted batch job 249567
jobid = ['249566', '249567']

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [66]:
#jobid = ['205798', '205799']
In [67]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249566    gpu_p5   pseudo  cfor032  R       0:45      1 jean-zay-iam10

 Done!
In [68]:
controle_technique(jobid[0])
Train throughput: 1162.38 images/second
GPU throughput: 1164.65 images/second
epoch time: 1102.51 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.439615 sec (10.1%/91.5%) +/- 0.015320
loading step time average (IO + CPU to GPU transfer): 0.000860 sec +/- 0.001123

Click here to display the log file

TODO : vérifier que 2 traces ont bien été générées dans les répertoires profiler/<name>_<jobid>_bs512_is224/ et profiler/<name>_<jobid>_bs64_is224/ et sous la forme d'un fichier .json:

In [69]:
!tree profiler/
profiler/
├── pseudo_249566_bs512_is224
│   └── jean-zay-iam10_1529868.1729184744185269755.pt.trace.json
└── pseudo_249567_bs64_is224
    └── jean-zay-iam12_3791290.1729184734641036112.pt.trace.json

2 directories, 2 files

Visualisation des traces profiler avec TensorBoard ¶

TODO : visualiser cette trace grâce à l'application TensorBoard en suivant les étapes suivantes :

  • ouvrir jupyterhub.idris.fr dans un nouvel onglet du navigateur
  • ouvrir une nouvelle instance JupyterHub en cliquant sur Add New JupyterLab Instance
  • sélectionner Spawn server on SLURM node (on va réserver un GPU)
  • sélectionner Tensorboard dans le menu Frontend
  • définir le chemin des logs $WORK/DLO-JZ/Jour1/tp_dlojz_jour1/profiler dans TensorBoard logs directory
No description has been provided for this image
  • sélectionner l'option avancée --partition=Octo-GPU A100 SXM4 with 80 GB GPU mem
No description has been provided for this image
  • lancer l'instance TensorBoard
    No description has been provided for this image

Remarque : le premier démarrage de TensorBoard peut prendre un peu de temps. Il faut parfois faire preuve d'un peu de patience lorsqu'on utilise cet outil mais ça en vaut la peine :)

Vous disposez de 2 traces : une avec un batch_size de 512 et l'autre avec un batch_size de 64.

TODO : en naviguant dans les différents onglets du TensorBoard, chercher à répondre aux questions suivantes :

  • Comparer les 2 traces : le GPU est-il bien utilisé ? (mémoire max utilisée, occupancy, efficiency)
  • les TensorCores sont-ils bien sollicités ? Quelles sont les couches éligibles aux TensorCores? (Voir les vue Operator et GPU Kernel)
  • essayer de repérer les grandes étapes de calcul sur la timeline de l'exécution (onglet Trace)

IMPORTANT : une fois le TP terminé, penser à quitter l'instance JupyterHub pour libérer le GPU ( > Hub Control Panel > Cancel ).

Optionnel : Profiler la validation¶

TODO : dans le script dlojz1_4.py :

  • Ajouter des balises comme suit :
# Runs the forward pass with no grad mode.
    with torch.no_grad():
        # Implement autocasting
        with autocast():
            with record_function("inference"): val_outputs = model(val_images)
            with record_function("loss function"): val_loss = criterion(val_outputs, val_labels)
  • Enlever l'indication de fin d'itération d'apprentissage.
  • A la place, indiquer au profiler la fin de chaque itération de validation (dans la boucle de validation).
  • Reprendre une trace profiler
  • La visualiser sur Tensorboard

Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.

In [78]:
command = f'./dlojz1_4.py -b 512 --image-size {image_size} --test --test-nsteps 8'
n_gpu = 1
jobid = gpu_jobs_submitter(command, n_gpu, MODULE, name=name,
                    account=account, time_max='00:10:00')
print(f'jobid = {jobid}')
batch job 0: 1 GPUs distributed on 1 nodes with 1 tasks / 1 gpus per node and 8 cpus per task
Submitted batch job 249578
jobid = ['249578']

Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.

In [79]:
#jobid = ['159708']
In [80]:
display_slurm_queue(name)
             JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
            249578    gpu_p5   pseudo  cfor032  R       0:44      1 jean-zay-iam10

 Done!
In [81]:
controle_technique(jobid)
Train throughput: 1169.68 images/second
GPU throughput: 1170.68 images/second
epoch time: 1095.63 seconds
-----------
training step time average (fwd/bkwd on GPU): 0.437353 sec (6.4%/94.0%) +/- 0.006370
loading step time average (IO + CPU to GPU transfer): 0.000375 sec +/- 0.000117

Click here to display the log file

TODO : dans le script dlojz1_4.py :

  • Vous pouvez ensuite changer la valeur de la variable VAL_BATCH_SIZE=250
  • Reprendre une trace profiler
  • La visualiser sur Tensorboard

Garage